// Package archive 提供文件和文件夹的压缩与解压功能
// 支持格式: zip, tar, tar.gz
// 支持压缩和解压单个文件、多个文件或文件夹
package archive

import (
	"archive/tar"
	"archive/zip"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// ExtractOptions 解压选项
type ExtractOptions struct {
	Destination string // 解压目标目录
}

// Extract 解压文件
func Extract(source string, options ExtractOptions) error {
	// 检查源文件是否存在
	if _, err := os.Stat(source); os.IsNotExist(err) {
		return fmt.Errorf("source file does not exist: %s", source)
	}

	// 确保目标目录存在
	if err := os.MkdirAll(options.Destination, 0755); err != nil {
		return fmt.Errorf("failed to create destination directory: %v", err)
	}

	// 根据文件扩展名选择解压方法
	switch {
	case strings.HasSuffix(source, ".zip"):
		return extractZip(source, options.Destination)
	case strings.HasSuffix(source, ".tar"):
		return extractTar(source, options.Destination, false)
	case strings.HasSuffix(source, ".tar.gz"), strings.HasSuffix(source, ".tgz"):
		return extractTar(source, options.Destination, true)
	default:
		return fmt.Errorf("unsupported archive format: %s", source)
	}
}

// extractZip 解压zip文件
func extractZip(source, dest string) error {
	reader, err := zip.OpenReader(source)
	if err != nil {
		return err
	}
	defer reader.Close()

	for _, file := range reader.File {
		// 构造目标路径
		path := filepath.Join(dest, file.Name)

		// 检查是否有路径遍历漏洞
		if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("illegal file path: %s", path)
		}

		// 如果是目录，创建目录
		if file.FileInfo().IsDir() {
			if err := os.MkdirAll(path, file.Mode()); err != nil {
				return err
			}
			continue
		}

		// 确保父目录存在
		if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
			return err
		}

		// 打开源文件
		srcFile, err := file.Open()
		if err != nil {
			return err
		}
		defer srcFile.Close()

		// 创建目标文件
		dstFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			return err
		}
		defer dstFile.Close()

		// 复制文件内容
		if _, err := io.Copy(dstFile, srcFile); err != nil {
			return err
		}
	}

	return nil
}

// extractTar 解压tar文件，可选gzip解压
func extractTar(source, dest string, useGzip bool) error {
	// 打开源文件
	file, err := os.Open(source)
	if err != nil {
		return err
	}
	defer file.Close()

	var tr *tar.Reader
	if useGzip {
		gr, err := gzip.NewReader(file)
		if err != nil {
			return err
		}
		defer gr.Close()
		tr = tar.NewReader(gr)
	} else {
		tr = tar.NewReader(file)
	}

	for {
		header, err := tr.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}

		// 构造目标路径
		path := filepath.Join(dest, header.Name)

		// 检查是否有路径遍历漏洞
		if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
			return fmt.Errorf("illegal file path: %s", path)
		}

		// 根据文件类型处理
		switch header.Typeflag {
		case tar.TypeDir:
			if err := os.MkdirAll(path, os.FileMode(header.Mode)); err != nil {
				return err
			}
		case tar.TypeReg:
			// 确保父目录存在
			if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
				return err
			}

			// 创建文件
			file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
			if err != nil {
				return err
			}
			defer file.Close()

			// 复制文件内容
			if _, err := io.Copy(file, tr); err != nil {
				return err
			}
		default:
			return fmt.Errorf("unsupported file type in tar: %v", header.Typeflag)
		}
	}

	return nil
}
